13章 テストダブル
13.1 テストダブルの、ソフトウェア開発への影響
コードベースがテストを念頭に設計されていないところであとからテストが必要であることが決定されると、テストダブルの利用に対応するにはコードのリファクタリングへの相当大きなコミットメントが必要
設計段階でテストを考えられてるかが大事。
13.2 Googleでのテストダブル
(モッキングフレームワークを使った)テストは、書くのは簡単だったとはいえ、バグをめったに発見しないわりに保守のための労力が定常的に必要となる
モック対象の動作が変わったとき、モックの動作の修正が漏れていてバグるのはあるある
モック対象側が「モックされている」という知識を持っていることはまずないので、実装時に検出する難易度もめっちゃ高い
と思ったらこの話題は13.5 で回収
13.3 基本概念
この辺の整理ってxUTP 以降にまとまった書籍って実はあまりなくって、GOOSとBDD本くらい?な気がする?ので、ここでもこうやって3つ整理されているのはいいなぁとおもった。 13.4 テストダブル利用のためのテクニック
13.5 本物の実装
古典的テストとモック主義者テストってロンドン派とかデトロイト派的なやつでしたっけ。(あまり詳しくない)
合ってた!
「テスト駆動開発」の付録Cに書いてあった記憶
OOPをどう捉えるかに主に3つの流派がある
抽象データ型として考える派、メッセージで考える派、プロトタイプで考える派
プロトタイプベースはあまり広まらず、JSもクラスベースに寄っていった
抽象データ型として捉える派はシーケンスとして考える。古典的テストと親和性が高い。静的型付けでボトムアップに設計していく。インタフェース。
メッセージで考える派はグラフとして考える。モック主義者テストと親和性が高い。動的型付けでトップダウンに設計していく。ダックタイピング。GOOS。
ユニットテストがテストダブルに依存しすぎると、同じレベルの信頼を得るために、エンジニアはインテグレーションテストを実行するか、もしくは手動でそれらの機能が期待通りに動作しているかを検証しなければならない。
裏を返せば、多量のインテグレーションテストや手動テストをさけるための1つの解として「テストダブルを使いすぎない」が有効だと言えそう。
本を読んでるときは「それはそうじゃん」と思うが、現場でどの程度テストダブルに依存したユニットテストになっているか今一度確認したい。
実際、障害の発生はほぼモックしている箇所からだった
テスト対象システムの出力が現在時刻に依存して変わる可能性があることを前提とした、システムクロックに依存するコードがある。この場合、システムクロックに依存するのではなく、特定の時刻をハードコードしているテストダブルをテストに利用できる
重要な前提が抜け落ちていて、これをやるなら同値分割きっちりやった上で全てのパターンにテストを実装しなくてはいけない
朝9時までは落ちるテスト、2月だけ落ちるテスト、大晦日だけは落ちるテストなどが生まれる
土曜に出勤したら土曜だけ落ちるテストを発見してしまい、泣く泣く直したことがあります😢
13.6 フェイキング
フェイクの実装は結構つくりこんだなーっていう記憶が多数ある。フェイクがないと仕事にならない。。。
何年も保守するプロダクトの基盤になりそうなライブラリ部分はフェイクをつくるとうまくいくけど、そうじゃない場合にはあまりコストメリットがでないなーっていう感覚があって、書籍でもどうようだった。
機能するDIはTDDのプロセスでつくられるんだけど、あんまり語られないよね。
1st
RichCalcTest.java
test(){
var calc = new RichCalc();
assert calc.add(1,2) == 3
}
interface Calc{
add(i1,i2)
}
class RichCalc{
add(i1, i2){
return i1 + i2;
}
}
2nd
RichCalcTest.java
test(){
var calc = new RichCalc();
assert calc.add(1,2) == 3
}
test2(){
var calc = new RichCalc();
assert calc.sub(1,2) == -1
}
interface Calc{
add(i1,i2)
sub(i1,i2)
}
class RichCalc{
add(i1, i2){
return i1 + i2;
}
sub(i1, i2){
return i1 - i2
}
}
3rd
RichCalcTest.java
test(){
var calc = new RichCalc();
assert calc.add(1,2) == 3
}
test2(){
var calc = new RichCalc();
assert calc.sub(1,2) == -1
}
Calc.java
interface Calc{
add(i1,i2)
sub(i1,i2)
}
RichCalc.java
class RichCalc{
add(i1, i2){
return i1 + i2;
}
sub(i1, i2){
return i1 - i2
}
}
interfaceのある言語は、interfaceを定義すること自体が差し替える前提と捉えられるのが良い
差し替える前提なので、そこにフェイキングがされるのも自然
13.7 スタビング
テスト対象システムをある状態に遷移させるために関数が特定の値を返さなければならない場合である
結合テストより上のレイヤでは、スタブしていた箇所で不具合が発生すると暗黙のうちにオブジェクトの状態に依存している箇所を見つけられそう
逆に単体テストの場合は、「関数が特定の値を返さなければならない」状態をテストデータ作成で実現できないのは実装に問題あり
「特定の値」が間違っているケースもままある
FC版ドラクエ2はテストプレイ時の想定レベル設定が誤っていたことでとんでもない難易度になったんだとか
13.8 インタラクションテスト
インタラクションテスト
この名前自体を初めて知った
モックライブラリ界隈の言い方
Javaのモックライブラリだとよく使われている
13.9 結論
13.10 要約
テスト内で本物の実装が利用できないなら、フェイクが理想的な解法である場合が多い。
「実装増えるじゃん」に対する模範解答を用意したい笑
自分で試行回数を増やすことでフェイクに対する解像度を上げ、有効性をうまく説明できるようにしたい。
この章が個人的に第3部の中でも少し浮いてる印象があった。(うまく言語化できないけど)このテストダブルの章はGoogleにおける自動テストの重要さから入れねばらなぬとなったのかな
確かに粒度がちょっと変かも
テストダブルについて語ると分量が章レベルになったのかも?
ユニットテストの章に入れても大規模テストの章に入れても違和感はある
ついでにいうとこの章だけベストプラクティスではなくトレードオフについて書かれている気がする
信頼不能テストの解消の方が主題だが、場合によってはテストダブルが唯一の解決策になるので書かざるを得なかったのかもしれない
Googleがどう扱っているか、というより教科書的なことが書かれている
使わないほうが嬉しい技術だから?
みんなからのコメント
Googleにしかできないとしたら、それはなぜか?
個々の章の部分的な理解は出来ていたものの改めて11章〜13章までの内容を踏まえトレードオフの判断もした上で自動テスト全体の設計が出来ているかと問われると結構ハードルが高いと思ってしまった。
現場でどこまでできているか?
できるだけ本物でやるっていうのはできているかも?一方でユニットテストが不足しているなとも感じる。
この本があるのになぜ実践する企業はすくないのか?
きれいなコードと一緒の問題ですけど、設計を変更せずに動くインクリメントを早くリリースしたいからっていうのはありそう。
自動テスト全般の話になってしまうかもですが、ビジネスと開発が分離してしまっていて投資判断をする人に理解してもらいづらい。何か問題が起きてから出ないと対処できない。
それの乗り越え方はなにか?
SonarQubeとかESLintとかで、テストコード側の技術的負債も見えるようにしていくとか、テストダブル利用に関する静的解析ツールの作成?とかかなぁ。SonarQubeくらいの技術的負債の返却日数みたく見えているといい気はする。
ステップを小さくするとしたらどうできそうか?
https://scrapbox.io/files/6458e50bf0a49f36cc2bbfea.png
https://scrapbox.io/files/6458e5332041f9437f68e3f7.png